package cn.spring.framework.redis.core.redis;

import cn.spring.framework.redis.config.LzmhRedisProperties;
import cn.spring.framework.redis.core.CacheOperator;
import cn.spring.framework.redis.exception.RetryRecoverException;
import cn.spring.framework.redis.mock.Mock;
import cn.spring.framework.redis.mock.MockRegister;
import cn.spring.framework.redis.service.RedisService;

import java.lang.reflect.Method;
import java.util.Iterator;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * @author xingkong
 * @Title: FailbackCacheOperator
 * @Description: 失败降级，后台定时重试策略
 * @date 2021/11/18 15:33
 */
public class FailbackCacheOperator {

    private final LzmhRedisProperties lzmhRedisProperties;

    private final RedisService redisService;

    private final CacheOperator cacheOperator;

    /**
     * 当前失败重试次数
     */
    private final AtomicInteger currRetryTime = new AtomicInteger();

    /**
     * 定时检测redis是否恢复
     */
    private final ScheduledExecutorService checkExecutor = Executors.newScheduledThreadPool(1);
    private volatile ScheduledFuture<?> checkFuture;
    /**
     * mock降级实现类
     */
    private final MockRegister mockRegister = MockRegister.getInstance();

    /**
     * 降级控制开关
     */
    private final AtomicBoolean block = new AtomicBoolean();

    public FailbackCacheOperator(LzmhRedisProperties lzmhRedisProperties,
                                 RedisService redisService,
                                 CacheOperator cacheOperator) {
        this.lzmhRedisProperties = lzmhRedisProperties;
        this.redisService = redisService;
        this.cacheOperator = cacheOperator;
    }

    public Object invoke(Method method, Object[] args) {
        if (this.block.get()) {
            //中断后，走降级逻辑
            return doMock(method, args);
        }

        while (this.currRetryTime.get() < this.lzmhRedisProperties.getRetryTime()) {
            try {
                Object result = method.invoke(this.cacheOperator, args);
                //重置计算器
                this.currRetryTime.set(0);
                return result;
            } catch (Exception t) {
                t.printStackTrace();
                this.currRetryTime.incrementAndGet();
            }
        }

        this.block.set(true);
        this.addScheduleCheckRecover();
        return doMock(method, args);
    }

    /**
     * 定时检测redis是否已经恢复
     */
    protected void addScheduleCheckRecover() {
        long retryPeriod = lzmhRedisProperties.getRetryPeriod();
        if (this.checkFuture == null) {
            synchronized (this.checkExecutor) {
                if (this.checkFuture == null) {
                    this.checkFuture = checkExecutor.scheduleWithFixedDelay(() -> {
                        try {
                            checkRecover();
                        } catch (Exception t) {
                            t.printStackTrace();
                        }
                    }, retryPeriod, retryPeriod, TimeUnit.MILLISECONDS);
                }
            }
        }
    }

    /**
     * 重试redis是否恢复
     */
    protected void checkRecover() throws RetryRecoverException {
        if (this.block.get()) {
            //测试连接是否恢复
            this.test();
            //恢复正常操作
            this.recover();
        }
    }

    /**
     * redis恢复正常
     */
    protected void recover() {
        //连接恢复
        this.block.set(false);
        //重置次数
        this.currRetryTime.set(0);
        if (this.checkFuture != null) {
            this.checkFuture.cancel(true);
            this.checkFuture = null;
        }
    }

    /**
     * 测试redis是否恢复
     */
    protected void test() throws RetryRecoverException {
        String key = "test_key";
        String value = "test_value";
        try {
            this.redisService.setCacheObject(key, value, 1L, TimeUnit.SECONDS);
        } catch (Exception t) {
            throw new RetryRecoverException("redis test recover error", t);
        }
    }

    /**
     * redis失败后走降级逻辑
     *
     * @return 返回降级逻辑的结果
     */
    private Object doMock(Method method, Object[] args) {
        String key = (String) args[0];
        //获取mock列表
        Iterator<Mock> mockCacheOperators = this.mockRegister.getMockCacheOperators();
        while (mockCacheOperators.hasNext()) {
            Mock mock = mockCacheOperators.next();
            //执行mock逻辑
            Object result = mock.mock(key, method);
            if (result != null) {
                return result;
            }
        }
        return null;
    }
}
